#include <stdlib.h>
#include <sys/epoll.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include "wrap.h"
#define SERV_PORT 8080
#define MAX_EVENTS 1024
#define BUFLEN 4096
int g_efd;
//描述就绪文件描述符相关信息(不带listenfd)
struct myevent_s {
        int fd;//要监听的文件描述符
        int events;//对应的监听事件
        void *arg;//泛型参数(其实它指向自己myevent_s)
        void (*call_back)(int fd, int events, void *arg);//回调函数
        int status;//是否在监听:1->在红黑树上(监听), 0->不在(不监听)
        char buf[BUFLEN];
        int len; //buf实际存放的有效字节数相当于strlen(buf)
        long last_active;//记录每次加入红黑树的时间
};
struct myevent_s g_events[MAX_EVENTS+1];//自定义结构体类型数组 + 1(这个1是listenfd)
//初始化结构体myevent_s
void eventset(struct myevent_s *ev, int fd, void (*callback)(int ,int, void*), void *arg)
{
        ev->fd = fd;
        ev->call_back = callback;
        ev->events = 0;
        ev->arg = arg;
        ev->status = 0;
        ev->last_active = time(NULL);
}
//向epoll监听的红黑树添加一个文件描述符
void eventadd(int epfd, int events, struct myevent_s *ev)
{
        struct epoll_event epv = {0,{0}};
        int op;
        epv.events = ev->events = events; //EPOLLIN或者EPOLLOUT
        epv.data.ptr = ev;

        if(ev->status == 1){//已经在红黑树里面了 修改
                op = EPOLL_CTL_MOD; 
        }else{ //不在红黑树里面 是增加 并且加入到红黑树上 设置status为1
                op = EPOLL_CTL_ADD;
                ev->status = 1;
        }
        if(epoll_ctl(epfd,op,ev->fd,&epv) < 0){//添加或者修改
                printf("event add failed fd=%d, events[%d]\n",ev->fd,events);
        }else{
                printf("event add OK  fd=%d, op=%d, events = %0X \n",ev->fd, op, events);
        }
}
void eventdel(int efd, struct myevent_s *ev)
{
        struct epoll_event epv = {0,{0}};

        if(ev->status != 1){ //不在红黑树上
                return;
        }

        epv.data.ptr = ev;
        ev->status = 0; //修改状态
        epoll_ctl(efd,EPOLL_CTL_DEL,ev->fd,&epv); //从红黑树efd 上 ev->fd 摘除
}
void senddata(int fd, int events, void *arg)
{
        struct myevent_s *ev = (struct myevent_s *)arg;
        int len;

        len = send(fd,ev->buf,ev->len,0); //直接将数据 回写给客户端

        if(len > 0){
                printf("send[fd=%d], [%d]%s\n",fd,len,ev->buf);
                eventdel(g_efd,ev);//从红黑树g_efd中移除
                eventset(ev,fd,recvdata,ev);//将该fd的回调改为recvdata
                eventadd(g_efd,EPOLLIN,ev);//将新添加到红黑树上,设置监听读事件
        }else{
                Close(ev->fd); //关闭连接
                eventdel(g_efd,ev);//从红黑树g_efd中移除
                printf("send[fd=%d] error %s\n",fd,strerror(errno));
        }
}
void recvdata(int fd, int events, void *arg)
{
        struct myevent_s* ev = (struct myevent_s *)arg;
        int len;
        
        len = recv(fd, ev->buf,sizeof(ev->buf), 0);

        eventdel(g_efd, ev); //从树上摘掉

        if(len > 0){
                ev->len = len;
                ev->buf[len] = '\0';
                printf("c[%d]:%s\n", fd, ev->buf);

                eventset(ev, fd, senddata, ev); //设置该fd对应的回调函数为senddata
                eventadd(g_efd,EPOLLOUT,ev); //将fd加入红黑树g_efd中,监听其写事件
        }else if(len == 0){
                Close(ev->fd);
                //ev-g_events 地址相减得到偏移元素的位置
                printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
        }else{
                Close(ev->fd);
                printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));       
        }
}
void acceptconn(int lfd,int events, void* arg)
{
        struct sockaddr_in cin;
        socklen_t len = sizeof(cin);
        int cfd,i;

        if( (cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1 ){
                if(errno!=EAGAIN && errno != EINTR){
                        //暂时不做处理
                }
                printf("%s: accept, %s\n",__func__, strerror(errno));
                return ;
        }

        do
        {
                for (i = 0; i < MAX_EVENTS; i++){
                        if(g_events[i].status == 0){//从g_events中找到一个空闲的元素
                                break;
                        }
                }

                if(i == MAX_EVENTS){//如果找到的是最大的 就退出了
                        printf("%s: max connect limit %d\n",__func__,MAX_EVENTS);
                        break;
                }

                int flag = 0;
                if( (flag = fcntl(cfd,F_SETFL,O_NONBLOCK)) < 0 ){//设置cfd为非阻塞
                        printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
                        break;
                }

                eventset(&g_events[i], cfd, recvdata, &g_events[i]);
                eventadd(g_efd, EPOLLIN, &g_events[i]);
        } while(0); //代码技巧: 如果希望在一个代码块只执行一次 有某种条件达到并能够退出去 就使用do{}while(0);

        printf("new connect %s:%d time:%ld, pos:%d\n",
                inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),g_events[i].last_active);
}
void initlistensocket(int epfd, short port)
{
        int lfd = Socket(AF_INET,SOCK_STREAM,0);
        fcntl(lfd,F_SETFL,O_NONBLOCK);//将监听的lfd设置为非阻塞

        //void eventset(struct myevent_s *ev, int fd, void (*callback)(int ,int, void*), void *arg);
        //&g_events[MAX_EVENTS]是数组里面最后一个元素的地址
        eventset(&g_events[MAX_EVENTS],lfd,acceptconn,&g_events[MAX_EVENTS]);

        //void eventadd(int epfd, int events, struct myevent_s *ev)
        eventadd(epfd,EPOLLIN,&g_events[MAX_EVENTS]);

        struct sockaddr_in sin;
        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(port);
        sin.sin_addr.s_addr = htonl(INADDR_ANY);
        Bind(lfd,(struct sockaddr *)&sin,sizeof(sin));

        Listen(lfd,20);
}
int main(int argc, char const *argv[])
{
        unsigned short port = SERV_PORT;
        if(argc == 2){
                port = atoi(argv[1]); //使用用户指定的端口,如未指定,用默认端口
        }

        g_efd = epoll_create(MAX_EVENTS+1); //创建红黑树,返回给全局 g_efd
        if(g_efd <= 0){
                printf("create efd in %s error %s\n",__func__,strerror(errno));
        }

        initlistensocket(g_efd,port); //初始化监听的socket

        struct epoll_event events[MAX_EVENTS+1];

        printf("server running:port[%d]\n",port);

        int checkpos = 0;
        int i;
        while (1){
                //超时验证,每次测试100个连接,不测试listenfd 当客户端60s内没有和服务器通讯,则关闭此客户端连接
                long now = time(NULL);//当前的时间
                for (i = 0; i < 100; i++, checkpos++){//一次循环检测100个，使用checkpos控制检测对象
                        if(checkpos == MAX_EVENTS){ //检测到最后一个将checkpos=0
                                checkpos = 0;
                        }
                        if(g_events[checkpos].status != -1){ //不在红黑树上
                                continue;
                        }
                        long duration = now  - g_events[checkpos].last_active;//客户端不活跃的时间
                        if(duration >= 60){
                                Close(g_events[checkpos].fd);//关闭与该客户端连接
                                printf("[fd=%d] timeout\n",g_events[checkpos].fd);
                                eventdel(g_efd,&g_events[checkpos]);//将该客户端从红黑树g_efd移除
                        }
                }
                //监听红黑树g_efd,将满足事件的文件描述符加至events数组中,1s没有事件满足,返回0
                int nfd = epoll_wait(g_efd,events, MAX_EVENTS+1,1000);
                if(nfd<0){
                        printf("epoll_wait error, exit \n");
                        break;
                }
                for (i = 0; i < nfd; i++){
                        //使用自定义结构myevent_s类型的指针, 接收 联合体data的void *ptr成员
                        struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
                        if((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)){ //读事件就绪
                                ev->call_back(ev->fd, events[i].events , ev->arg);
                        }
                        if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)){ //写事件就绪
                                ev->call_back(ev->fd, events[i].events , ev->arg);
                        }       
                }
        }
        //退出前释放所有资源
        return 0;
}
